package com.uniledger.exchange.mine.scheme.keystore;

import android.text.TextUtils;

import com.google.gson.Gson;
import com.lambdaworks.codec.Base64;
import com.orhanobut.logger.Logger;
import com.uniledger.commonlib.tweetnacl.TweetNaclFast;

import org.spongycastle.crypto.generators.SCrypt;
import org.stellar.sdk.KeyPair;

import java.io.File;
import java.security.SecureRandom;

import io.github.novacrypto.bip39.MnemonicGenerator;
import io.github.novacrypto.bip39.SeedCalculator;
import io.github.novacrypto.bip39.Words;
import io.github.novacrypto.bip39.wordlists.English;
import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;

public class StellarKeystore {

    public ScryptOptions latestScryptOptions;

    public StellarKeystore() {
        latestScryptOptions = new ScryptOptions();
    }

    /**
     * Retrieves a public key from a keystore file.
     *
     * @param keyPair
     * @return
     */
    String publicKey(WalletData keyPair) {
        return keyPair.address;
    }

    String publicKey(String keystore) {
        return fileContents(keystore).address;
    }

    /**
     * Retrieves a stellar keypair from a keystore file.
     *
     * @param keystore<Blob|Object>
     * @param password
     * @returns {Promise<StellarSdk.Keypair>}
     */
    public KeyPair keypair(String keystore, String password) {
        WalletData walletData = new Gson().fromJson(keystore, WalletData.class);
        return keypair(walletData, password);
    }

    public KeyPair keypair(WalletData keystore, String password) {

        WalletData keystoreData = keystore;

        byte[] key = keyFromPassword(password, Base64.decode(keystoreData.crypto.salt.toCharArray()));

        byte[] secretKey = new TweetNaclFast
                .SecretBox(key)
                .open(Base64.decode(keystoreData.crypto.ciphertext.toCharArray()),
                        Base64.decode(keystoreData.crypto.nonce.toCharArray()));

        //TODO 判断是否正确的密钥对
        KeyPair keyPair = KeyPair.fromSecretSeed(UrlEncodingHelper.encodeUtf8(new String(secretKey)));

        Logger.d(keyPair.getAccountId());

        if (TextUtils.equals(keyPair.getAccountId(), keystoreData.address)) {//正确
            return keyPair;
        } else {//密码错误
            return null;
        }
    }

    /**
     * 从文件加载Keystore
     *
     * @return
     */
    public WalletData fileContents(String keystore) {
        WalletData walletData = new Gson().fromJson(keystore, WalletData.class);
        return walletData;
    }

    public String fileContents(File file) {

        return "";
    }


    /**
     * 随机生成的钱包
     *
     * @param password
     * @param filename
     */
    public void createAndDownload(String password, String filename) {
        WalletData walletData = create(password, null);
        download(filename, walletData);
    }

    /**
     * 使用助记词出的的HD钱包
     *
     * @param password
     * @param filename
     * @param keyPair
     */
    public void createAndDownload(String password, String filename, KeyPair keyPair) {
        WalletData walletData = create(password, keyPair);
        download(filename, walletData);
    }

    /**
     * 下载文件
     *
     * @param filename
     * @param walletData
     */
    private void download(String filename, WalletData walletData) {
        String keystore = new Gson().toJson(walletData);
        //TODO 打包成文件
    }


    /**
     * Creates a keystore file's contents using a provided password and the provided keypair or random keypair. Returns a json object representing the keypair file.
     *
     * @param password
     * @param keyPair
     * @returns {Promise<{keypair: StellarSdk.Keypair, fileData: {}}>}
     */
    public WalletData create(String password, KeyPair keyPair) {
        KeyPair newKeyPair = keyPair != null ? keyPair : KeyPair.random();
        byte[] salt = TweetNaclFast.randombytes(32);
        byte[] key = keyFromPassword(password, salt);
        byte[] nonce = randomNonce();
        byte[] mgs = UrlEncodingHelper.decodeUtf8(String.valueOf(newKeyPair.getSecretSeed())).getBytes();
        byte[] ciphertext = new TweetNaclFast.SecretBox(key).box(mgs, nonce);
        WalletData walletData = new WalletData();
        walletData.address = newKeyPair.getAccountId();
        WalletData.Crypto crypto = new WalletData.Crypto();
        crypto.ciphertext = String.valueOf(Base64.encode(ciphertext));
        crypto.nonce = String.valueOf(Base64.encode(nonce));
        crypto.salt = String.valueOf(Base64.encode(salt));
        crypto.scryptOptions = latestScryptOptions;
        walletData.crypto = crypto;
        Logger.d(new Gson().toJson(walletData));
        return walletData;
    }

    private byte[] keyFromPassword(String password, byte[] salt) {
        ScryptOptions options = latestScryptOptions;
        return generateDerivedScryptKey(password.getBytes(), salt, options.N, options.r, options.p, options.dkLen);
    }

    private static byte[] generateDerivedScryptKey(
            byte[] password, byte[] salt, int n, int r, int p, int dkLen) {
        return SCrypt.generate(password, salt, n, r, p, dkLen);
    }

    private byte[] randomNonce() {
        return TweetNaclFast.randombytes(TweetNaclFast.SecretBox.nonceLength);
    }

    public KeyPair fromKeyPairMnemonic() {
        return fromKeyPairMnemonic("");
    }

    public KeyPair fromKeyPairMnemonic(String passphrase) {
        String mnemonic = generateNewMnemonic(Words.TWELVE);
        final byte[] seed = new SeedCalculator().calculateSeed(mnemonic, passphrase);
        return KeyPair.fromSecretSeed(seed);
    }

    private String generateNewMnemonic(Words wordCount) {
        StringBuilder sb = new StringBuilder();
        byte[] entropy = new byte[wordCount.byteLength()];
        new SecureRandom().nextBytes(entropy);
        new MnemonicGenerator(English.INSTANCE)
                .createMnemonic(entropy, sb::append);
        return sb.toString();
    }

    Single<KeyPair> rxKeypair(String keystore, String password) {
        return Single.create(new SingleOnSubscribe<KeyPair>() {
            @Override
            public void subscribe(SingleEmitter<KeyPair> emitter) throws Exception {
                emitter.onSuccess(keypair(keystore, password));
            }
        });
    }



}
